# 线程:Thread    进程: Process
"""
考虑？创建线程？如何使用线程？
t1 = threading.Thread(target=download, name="aa", args=(1,))
    t1.start()

自定义线程：
class MyThread(Thread):
    def __init__(self, name):
        super().__init__()
        self.name = name

    def run(self):
        任务

t = MyThread("name")
t.start()

线程：
    新建  就绪  运行  阻塞  结束
"""
import queue
import threading
import time


def download(n):
    images = ["boy.jpg", "girl.jpg", "man.jpg"]
    for image in images:
        print("正在下载：", image)
        time.sleep(n)
        print(f"下载{image}成功！")


def listen_music():
    musics = ["缺氧", "匆匆那年", "偷心", "再回首", "我的未来不是梦"]
    for music in musics:
        time.sleep(0.5)
        print(f"正在听{music}歌！")


if __name__ == "__main__":
    # 线程对象
    t1 = threading.Thread(target=download, name="aa", args=(1,))
    t1.start()

    t2 = threading.Thread(target=listen_music, name="bb")
    t2.start()

    # n = 1
    # while True:
    #     print(n)
    #     time.sleep(1.5)
    #     n += 1

"""
线程是可以共享全局变量的

GIL  全局解释器锁  -----> 伪线程
python底层只要用线程默认加锁，但是在CPU运行巨量数据时会自动释放

进程共享数据与线程共享数据的区别：
进程是每个进程中都有一份数据
线程是共同使用一个数据 -----> 数据安全性问题

共享数据情况:
加锁后会保证数据安全，即数据同步正常（运行速度慢）
不加锁后数据会不安全，出现运算结果和真实数据对不上的情况（运行速度快）  

进程：计算密集型
线程：耗时操作，爬虫，IO操作
"""
money = 1000


def run1():
    global money
    for i in range(100):
        money -= 1


def run2():
    global money
    for i in range(100):
        money -= 1


if __name__ == "__main__":
    # 创建线程
    th1 = threading.Thread(target=run1, name="th1")
    th2 = threading.Thread(target=run2, name="th2")
    # 启动
    th1.start()
    th2.start()

    th1.join()
    th2.join()

    print('money:', money)

"""
共享数据：
如果多个线程共同对某个数据修改，则可能出现不可预料的后果，
为了保证数据的正确性，需要对多个线程进行同步

同步：一个一个的完成，一个做完另一个才能进来，效率就会降低
使用Thread对象的Lock和Rlock可以实现简单的线程同步，这两个对象都有acquire方法和release方法，
对于那些需要每次只允许一个线程操作的数据，可以将其操作放到acquire和release方法之间

多线程的优势在于可以同时运行多个任务（至少感觉起来是这样），
但是当线程需要共享数据时，可能存在数据不同步的问题，为了避免这种情况，引入了锁的概念

数据量大的情况：
lock = threading.Lock()
lock.acquire()     请求得到锁
    ......
lock.release()     释放锁
只要不释放，其他的线程都无法进入运行状态
"""
import random

lock = threading.Lock()
list1 = [0] * 10     # 表示列表中有十个零


def task1():
    # 获取线程锁，如果已经上锁，则等待锁的释放
    lock.acquire()    # 阻塞
    for i in range(len(list1)):
        list1[i] = 1
        time.sleep(0.5)
    lock.release()    # 释放锁


def task2():
    lock.acquire()
    for i in range(len(list1)):
        print("---->", i)
        time.sleep(0.5)
    lock.release()


if __name__ == '__main__':
    t1 = threading.Thread(target=task1)
    t2 = threading.Thread(target=task2)

    t2.start()
    t1.start()

    t1.join()
    t2.join()

    print(list1)

# 死锁
"""
开发过程中使用线程，在线程间共享多个资源的时候，
如果两个线程分别占有一部分资源并且同时等待对方的资源，就会造成死锁
尽管死锁很少发生，但一旦发生就会造成应用的停止响应，程序不做任何事情

避免死锁：
1.重构代码
2.使用timeout参数
"""
from threading import Lock, Thread


lockA = Lock()
lockB = Lock()


class MyThread1(Thread):
    def run(self):     # start()
        if lockA.acquire():   # 如果可以获取到锁则返回True
            print(self.name+"获取到了A锁")
            time.sleep(0.5)
            if lockB.acquire(timeout=5):    # 阻塞
                print(self.name+"又获取了B锁，原来还有A锁")
                lockB.release()
            lockA.release()


class MyThread2(Thread):
    def run(self):
        if lockB.acquire():
            print(self.name+"获取到了B锁")
            time.sleep(0.5)
            if lockA.acquire(timeout=5):
                print(self.name+"又获取了A锁，原来还有B锁")
                lockA.release()
            lockB.release()


if __name__ == '__main__':
    t1 = MyThread1()
    t2 = MyThread2()

    t1.start()
    t2.start()

"""
生产者和消费者模式：两个线程之间的通信
可以使用队列来实现线程间的同步

Python的queue模块中提供了同步的、线程安全的队列类，包括FIFO（先出先入）队列Queue,
LIFO（后出先入）队列LifoQueue，和优先级队列PriorityQueue,
这些队列都实现了锁原理（可以理解为原子操作，即要么不做，要么就做完），能够在多线程中直接使用
"""


def produce(q):
    i = 0
    while i < 10:
        num = random.randint(1, 100)
        # %d是一个占位符，表示这里将插入一个整数; num表示要插入的整数值;
        q.put("生产者产生数据：%d" % num)   # %是字符串格式化操作符，将num的值插入到格式化字符串中的%d占位符位置
        print("生产者产生数据：%d" % num)
        time.sleep(1)
        i += 1
    q.put(None)
    # 完成任务
    q.task_done()


def consume(q):
    while True:
        item = q.get()
        if item is None:
            break
        print("消费者获取到：%s" % item)
        time.sleep(4)
    # 完成任务
    q.task_done()


if __name__ == '__main__':
    q = queue.Queue(10)
    arr = []
    # 创建生产者
    th = threading.Thread(target=produce, args=(q,))
    th.start()
    # 创建消费者
    tc = threading.Thread(target=consume, args=(q,))
    tc.start()

    th.join()
    tc.join()
    print("END")
